Integration des GLPK-Solvers in ein C#-Programm (Teil 1) Frameworks 

Integration des GLPK-Solvers in ein C#-Programm (Teil 1)

Im ersten Teil dieser Serie wird das Einbinden und Aufrufen des Solvers behandelt.

Da es sich bei der glpk-dll um nicht verwalteten C-Code handelt, der aus verwaltetem C#-Code heraus aufgerufen wird, muss zunächst folgender Namespace eingebunden werden:

using System.Runtime.InteropServices;

Des Weiteren ist der Pfad zur glpk-dll in einer konstanten String-Variable abzulegen. Der Pfad kann also nicht zur Laufzeit gesetzt werden:

const string glpkLibrary = "C:\...\glpk_4_34.dll"

Das DllImport-Attribut wird vor die Funktionsdeklaration jedes verwendeten glpk-Einstiegspunktes, d.h. jeder verwendeten glpk-Funktion, gesetzt. Die Signatur jeder Funktion muss dem Namen einer von der DLL exportierten Funktion entsprechen. Nicht zu vergessen sind außerdem die Schlüsselwörter static und extern vor jeder Funktion.

[DllImport(glpkLibrary, SetLastError = true)]
static extern double* glp_create_prob();

[DllImport(glpkLibrary, SetLastError = true)]
static extern int glp_add_rows(double* lp, int rows);

[DllImport(glpkLibrary, SetLastError = true)]
static extern int glp_add_cols(double* lp, int cols);

Jetzt können alle importierten GLPK-Funktionen im C#-Programm aufgerufen werden. Da dies kein glpk-Tutorial ist, möchte ich an dieser Stelle für eine Dokumentation im Umgang mit diesem Solver auf die GLPK-Webseite verweisen.

Der GLPK-Solver nimmt die linearen Constraints in Form einer Constraint-Matrix entgegen. Die C-Schnittstelle schreibt vor, dass an den Solver drei Arrays int* ia, int* ja, double* ar zu übergeben sind, wobei ia und ja die Zeilen- und Spaltenindizes enthalten, die zu den Constraint-Variablen mit den Koeffizienten in ar gehören.

Die drei Arrays können in Form von ArrayLists in einer inneren Klasse definiert werden. Ich werde gleich darauf eingehen, wie die ArrayLists in C-kompatible Zeiger umgewandelt werden können.

class ConstraintMatrix
{
    public ArrayList ia = new ArrayList();
    public ArrayList ja = new ArrayList();
    public ArrayList ar = new ArrayList();
}

Vorher aber noch ein kurzes Beispiel für die Erstellung eines glpk-Objektes, welches als double-Zeiger abgebildet wird, und den Eintrag einer Constraint-Variablen mit dem Koeffizienten 3.5 an die Position [1,1] der Constraint-Matrix. Achtung: bei GLPK wird mit 1-basiertem Index gearbeitet!

Neben dem Eintrag der Indizes und des Koeffizienten in die Arrays ia, ja und ar muss ggf. eine neue Zeile und Spalte im Solver hinzugefügt werden.

...
double* lp = glp_create_prob();

int rowIndex = glp_add_rows(this.lp, 1);
int colIndex = glp_add_cols(this.lp, 1);
double coeff = 3.5;

this.cm.ia.Add(rowIndex);
this.cm.ja.Add(colIndex);
this.cm.ar.Add(coeff);
...

Bevor die Funktion glp_simplex() aufgerufen werden kann, muss mit glp_load_matrix() noch die Constraint-Matrix übergeben werden. Diese Funktion benötigt aber Zeigervariablen, in welche die ArrayLists erst umgewandelt werden müssen. Dies geschieht, in dem die ArrayLists erst mit der Funktion ArrayList.ToArray(typeof(int)) in Integer-Arrays konvertiert und anschließend mit der fixed-Notation auf entsprechende Zeiger abgebildet werden. Nur in einem fixed-Block ist die Zuweisung von Zeigern auf verwaltete Variablen möglich, da innerhalb des Anweisungsblockes dann keine Verschiebung der Variablen durch den Garbage Collector stattfinden kann (Fixierung). Die fixed-Anweisung ist nur in einesm unsafe-Kontext zulässig.

public bool Solve()
{
  int numRows = glp_get_num_rows(this.lp);
  int numCols = glp_get_num_cols(this.lp);

  unsafe
  {
    int[] ia = (int[])cm.ia.ToArray(typeof(int));
    int[] ja = (int[])cm.ja.ToArray(typeof(int));
    double[] ar = (double[])cm.ar.ToArray(typeof(double));

    fixed (int* iap = ia)
    {
    fixed (int* jap = ja)
    {
    fixed (double* arp = ar)
    {
      glp_load_matrix(lp, ia.Length - 1, iap, jap, arp);
    }
    }
    }
     int res = glp_simplex(this.lp, null);
     if (res == 0)
     {
       return true;
      }
      else
      {
        return false;
       }
     }
}

Related posts